/*
 * IdentityComonentDefinition.java
 *
 * Created on September 2, 2006, 4:37 PM
 *
 * (C) 2006 R. Alexander Milowski
 */

package org.infoset.component.library;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
import org.infoset.component.Component;
import org.infoset.component.ComponentBase;
import org.infoset.component.ComponentDefinitionBase;
import org.infoset.xml.Document;
import org.infoset.xml.Item;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;
import org.infoset.xml.jaxp.SAXTransformerSourceFilter;
import org.infoset.xml.util.DocumentDestination;
import org.infoset.xml.util.DocumentSource;

/**
 *
 * @author alex
 */
public class XSLTComponentDefinition extends ComponentDefinitionBase {
   
   static Logger LOG = Logger.getLogger(XSLTComponentDefinition.class.getName());
   static Name DOCUMENT_INPUT_PORT = Name.create("source");
   static Name TRANSFORM_INPUT_PORT = Name.create("transform");
   static Name RESULT_OUTPUT_PORT = Name.create("result");
   static List<Name> inputs = new ArrayList<Name>();
   static List<Name> outputs = new ArrayList<Name>();
   static {
      inputs.add(DOCUMENT_INPUT_PORT);
      inputs.add(TRANSFORM_INPUT_PORT);
      outputs.add(RESULT_OUTPUT_PORT);
   }
   
   public class XSLTComponent extends ComponentBase {
      
      SAXTransformerSourceFilter filter;
      boolean filterIsResource;
      Document inputDocument;
      boolean inputDocumentIsResource;
      ItemDestination output;
      URL filterLocation;
      Document filterDocument;
      boolean done;
      
      public XSLTComponent(Name name,String vendor,String version) {
         super(name,vendor,version);
         this.filter = null;
         this.filterIsResource = false;
         this.inputDocument = null;
         this.inputDocumentIsResource = false;
         this.output = null;
      }
      
      protected void bindParameters() {
         for (Name name : context.getParameterNames()) {
            filter.setParameter(name,context.getParameter(name));
         }
      }
      
      public void bindResource(Name port,Document document)
         throws XMLException 
      {
         if (port.equals(DOCUMENT_INPUT_PORT)) {
            // Save the input document
            inputDocument = document;
            inputDocumentIsResource = true;
         } else if (port.equals(TRANSFORM_INPUT_PORT)) {
            // Load the transform into a filter
            filter = new SAXTransformerSourceFilter();
            filterDocument = document;
            filterLocation = null;
            filter.loadTransform(document);
            bindParameters();
            if (output!=null) {
               filter.attach(output);
            }
            filterIsResource = true;
         } else {
            throw new XMLException("Binding port "+port+" via ItemSource is not supported.");
         }
      }
   
      public void bindResource(Name port,URI location)
         throws XMLException
      {
         if (port.equals(DOCUMENT_INPUT_PORT)) {
            try {
               inputDocument = context.getDocumentLoader().load(location);
               inputDocumentIsResource = true;
            } catch (IOException ex) {
               throw new XMLException("Cannot load input document due to I/O error.",ex);
            }
         } else if (port.equals(TRANSFORM_INPUT_PORT)) {
            try {
               filter = new SAXTransformerSourceFilter();
               filterLocation = location.toURL();
               filterDocument = null;
               filter.loadTransform(filterLocation);
               bindParameters();
               if (output!=null) {
                  filter.attach(output);
               }
               filterIsResource = true;
            } catch (MalformedURLException ex) {
               throw new XMLException("Cannot convert URI to URL for transform location",ex);
            }
         } else {
            super.bindResource(port,location);
         }
      }
      public void bindOutputPort(Name port,ItemDestination output)
         throws XMLException
      {
         if (port.equals(RESULT_OUTPUT_PORT)) {
            this.output = output;
            if (filter!=null) {
               filter.attach(output);
            }
         } else {
            throw new XMLException("Unknown output port "+port+" on "+name);
         }
      }

      
      public void onStart() 
         throws XMLException
      {
         if (filterIsResource && filter!=null) {
            // we have to reload because some implementations do not understand how to reuse transforms
            if (filterDocument!=null) {
               filter.loadTransform(filterDocument);
               bindParameters();
               if (output!=null) {
                  filter.attach(output);
               }
            }
            if (filterLocation!=null) {
               filter.loadTransform(filterLocation);
               bindParameters();
               if (output!=null) {
                  filter.attach(output);
               }
            }
         }
         done = false;
         if (filter!=null && inputDocument!=null) {
            // Perform the transform because we have all the inputs
            DocumentSource.generate(inputDocument,false,filter);
            done = true;
         }
      }
      
      public ItemDestination getInputPort(Name port) 
         throws XMLException
      {
         if (port.equals(DOCUMENT_INPUT_PORT)) {
            if (filter==null) {
               return new DocumentDestination() {
                  public void send(Item item) 
                     throws XMLException
                  {
                     super.send(item);
                     if (item.getType()==Item.ItemType.DocumentEndItem) {
                        inputDocument = getDocument();
                        if (filter!=null) {
                           // Perform the transform as we now have the inputDocument
                           DocumentSource.generate(inputDocument,false,filter);
                           done = true;
                        }
                     }
                  }
               };
            } else {
               return filter;
            }
         } else if (port.equals(TRANSFORM_INPUT_PORT)) {
            return new DocumentDestination() {
               public void send(Item item) 
                  throws XMLException
               {
                  super.send(item);
                  if (item.getType()==Item.ItemType.DocumentEndItem) {
                     Document doc = getDocument();
                     filter = new SAXTransformerSourceFilter();
                     filter.loadTransform(doc);
                     bindParameters();
                     if (output!=null) {
                        filter.attach(output);
                     }
                     if (inputDocument!=null) {
                        // Perform the transform as we now have the transform
                        DocumentSource.generate(inputDocument,false,filter);
                        done = true;
                     }
                  }
               }
            };
         } else {
            super.getInputPort(port);
         }
         return null;
      }
      
      public void onDocumentInput(Name port,Document document) 
         throws XMLException
      {
         if (port.equals(DOCUMENT_INPUT_PORT)) {
            if (inputDocument!=null) {
               throw new XMLException("The input document has already be receieved.");
            }
            inputDocument = document;
            
            if (filter!=null) {
               // Perform the transform as we now have the input document
               DocumentSource.generate(inputDocument,false,filter);
               done = true;
            }
         } else if (port.equals(TRANSFORM_INPUT_PORT)) {
            if (filter!=null) {
               throw new XMLException("The transform document has already be receieved.");
            }
            // Load the transform into a filter
            filter = new SAXTransformerSourceFilter();
            filter.loadTransform(document);
            if (output!=null) {
               filter.attach(output);
            }
            if (inputDocument!=null) {
               // Perform the transform as we now have the transform
               DocumentSource.generate(inputDocument,false,filter);
               done = true;
            }
         } else {
            super.getInputPort(port);
         }
      }
      
      public void onEnd()
         throws XMLException
      {
         if (!inputDocumentIsResource) {
            inputDocument = null;
         }
         if (!filterIsResource) {
            filter = null;
         }
         done = false;
      }
   }
   
   
   /** Creates a new instance of IdentityComonentDefinition */
   public XSLTComponentDefinition(Name name) {
      super(name,null,null,null);
   }
   
   /** Creates a new instance of IdentityComonentDefinition */
   public XSLTComponentDefinition(Name name,String vendor,String version,URI vendorLocation) {
      super(name,vendor,version,vendorLocation);
   }
   
   public Component newInstance() 
      throws XMLException
   {
      return new XSLTComponent(getName(),getVendor(),getVersion());
   }
   
   public Collection<Name> getInputs() {
      return inputs;
   }
   
   public Collection<Name> getOutputs() {
      return outputs;
   }

}
